//
//  LocationListenerViewController.m
//  ClienteHabitueIOS
//
//  Created by Mauro Carreño on 6/8/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//

#import "LocationListenerViewController.h"
#import "ControlVariables.h"
#import "SharedMemory.h"
#import "MBProgressHUD.h"

//#define MILISECONDS_TO_RETRY 10000  //10 secs
//#define METERS_BEFORE_RETRY 10;
#define TWO_MINUTES 120; //in secs

#define MIN_DESIRED_ACCURACY 200 //in meters
#define MAX_DESIRED_AGE 1800 //in seconds (30 minutes)

#define FAKE_LOCATION_LAT -34.603691 //Obelisco Buenos Aires
#define FAKE_LOCATION_LON -58.382212
#define EXTRA_LAST_LOCATION_LAT @"EXTRA_LAST_LOCATION_LAT"
#define EXTRA_LAST_LOCATION_LON @"EXTRA_LAST_LOCATION_LON"

static bool isWaitingForLocation = false;
static bool canceledWaiting = false;


//TODO-futuro: mover esto al App Delegate donde puedo detectar si se perdio foco o no (con applicationWillResignActive y applicationDidEnterBackground)
//TODO-futuro: como alternartiva, poner un NSTimer que detecte cuando se pierde el servicio (http://stackoverflow.com/questions/11062201/continously-check-for-location-services-enabled-in-ios)

//TODO: verificar que no se haya mostrado ya el cartel de error y evitar multiples carteles.

@interface LocationListenerViewController ()

@end

@implementation LocationListenerViewController

@synthesize locationManager;

- (void) dealloc
{
    [locationManager release], locationManager = nil ;
    [super dealloc];
}

- (void)viewDidUnload
{
    [self stopUpdatingLocation:nil];
    self.locationManager.delegate = nil;
    self.locationManager = nil ;
    [super viewDidUnload];
}

//OVERRIDE IF ITS A LOCATION WAITER
- (bool) isLocationWaiter
{
    return false;
}

+ (CLLocation *) getLastGoodLocation
{
    CLLocation * loc = [[SharedMemory sharedInstance] lastGoodLocation];
    if (loc==nil ) {
        CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(FAKE_LOCATION_LAT, FAKE_LOCATION_LON);
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        if([defaults objectForKey:EXTRA_LAST_LOCATION_LAT]!=nil){
            coord = CLLocationCoordinate2DMake([[defaults objectForKey:EXTRA_LAST_LOCATION_LAT]doubleValue], [[defaults objectForKey:EXTRA_LAST_LOCATION_LON]doubleValue]);
        }
        loc = [[[CLLocation alloc] initWithCoordinate:coord altitude:0 horizontalAccuracy:9999 verticalAccuracy:9999 timestamp:[NSDate date]] autorelease];
    }
    return loc;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}


- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view.
    // Create the manager object
    self.locationManager = [[[CLLocationManager alloc] init] autorelease];
    self.locationManager.delegate = self;
}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
     CLLocation* lastGoodLocation = [LocationListenerViewController getLastGoodLocation];
     if ([self isLocationWaiter] && !canceledWaiting && ! [self isLocationPreciseEnough:lastGoodLocation]) {
         isWaitingForLocation = true;
         
         MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
         hud.labelText = kLoadingWaitingLocation;
         hud.detailsLabelText = kLoadingWaitingLocationCancel;
         UITapGestureRecognizer *hudSingleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(positionWaitingCanceled:)];
         [hud addGestureRecognizer:hudSingleTap];
     }
}

//When the position waiting is canceled. Is equivalent to touching the "ignore" button on the popup.
-(void) positionWaitingCanceled:(UITapGestureRecognizer*)sender
{
    canceledWaiting = true;
    [self waitingFinished];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [self stopUpdatingLocation:nil];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults synchronize];
    [super viewWillDisappear:animated];
}

- (void)viewWillAppear:(BOOL)animated
{
    //[self.navigationController setNavigationBarHidden:YES animated:animated];
    [super viewWillAppear:animated];
    [self stopUpdatingLocation:nil];
    [self startReadingLocation];
}


- (void)startReadingLocation 
{
    /*if (((SharedMemory *)[SharedMemory sharedInstance]).localizationAvailable == NO) {
        return;
    }*/
    
    // Create the manager object
    //self.locationManager = [[[CLLocationManager alloc] init] autorelease];
    //self.locationManager.delegate = self;
    // This is the most important property to set for the manager. It ultimately determines how the manager will
    // attempt to acquire location and thus, the amount of power that will be consumed.
    self.locationManager.desiredAccuracy = kLocationAccuracyDesired;
    // When "tracking" the user, the distance filter can be used to control the frequency with which location measurements
    // are delivered by the manager. If the change in distance is less than the filter, a location will not be delivered.
    self.locationManager.distanceFilter = kLocationChangeSensivility;
    // Once configured, the location manager must be "started".
    if ([CLLocationManager locationServicesEnabled] && 
        [CLLocationManager authorizationStatus] != kCLAuthorizationStatusDenied) {
        [self.locationManager startUpdatingLocation];
    }
    else {
        [self stopUpdatingLocation:@"Error"];
    }
}

/**
 *  Override on children with super if something is needed after waiting finished.
 */
- (void) waitingFinished
{
    isWaitingForLocation = false;
    [MBProgressHUD hideHUDForView:self.view animated:YES];
}

- (bool) isFakeLocation:(CLLocation*) location
{
    if (location!=nil && ((AreSame(location.coordinate.latitude, FAKE_LOCATION_LAT) && AreSame(location.coordinate.longitude, FAKE_LOCATION_LON))
        || (AreSame(location.coordinate.latitude, (double)0.0) && AreSame(location.coordinate.longitude, (double)0.0)))) {
        return true;
    }
    return false;
}

bool AreSame(double a, double b)
{
    double EPSILON = 0.0001;
    return fabs(a - b) < EPSILON;
}

- (bool) isLocationPreciseEnough:(CLLocation*) location
{
    if (location!=nil && location.horizontalAccuracy>0 && location.horizontalAccuracy <= MIN_DESIRED_ACCURACY
        && ([[NSDate date] timeIntervalSince1970] - [[location timestamp] timeIntervalSince1970]) < MAX_DESIRED_AGE && ![self isFakeLocation:location]) {
        return true;
    }
    
    //Any location is better than the hardcoded location
    SharedMemory* mem = [SharedMemory sharedInstance];
    if (location!=nil && mem.lastGoodLocation!=nil && ![self isFakeLocation:mem.lastGoodLocation]) {
        return true;
    }
    return false;
}

/*
 * We want to get and store a location measurement that meets the desired accuracy. For this example, we are
 *      going to use horizontal accuracy as the deciding factor. In other cases, you may wish to use vertical
 *      accuracy, or both together.
 */
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation 
{
    // test that the horizontal accuracy does not indicate an invalid measurement
    if (newLocation==nil || newLocation.horizontalAccuracy < 0)
        return;
    
    SharedMemory* mem = [SharedMemory sharedInstance];
    if ( [self isBetterLocation:newLocation currentBestLocation:mem.lastGoodLocation]){
        mem.lastGoodLocation = newLocation;
        //Commit is done on the onPause for performance issues
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        [defaults setObject:[NSNumber numberWithDouble:mem.lastGoodLocation.coordinate.latitude]  forKey:EXTRA_LAST_LOCATION_LAT];
        [defaults setObject:[NSNumber numberWithDouble:mem.lastGoodLocation.coordinate.longitude] forKey:EXTRA_LAST_LOCATION_LON];
    }
    if (isWaitingForLocation && [self isLocationPreciseEnough:mem.lastGoodLocation] && [self isLocationWaiter] && !canceledWaiting) {
        [self waitingFinished];
    }
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error 
{
    // The location "unknown" error means the manager is currently unable to get the location. As another error has the same code, only comparing with desciptions could solve it
    if ([error code] != kCLErrorLocationUnknown || [[error description] isEqualToString:kSystemErrorDescriptionForLocation]) {
        [self stopUpdatingLocation:@"Error"];
    }
    //[self stopUpdatingLocation:@"Error"];
}

- (void)stopUpdatingLocation:(NSString *)state 
{
    if (self.locationManager !=nil) {
        [self.locationManager stopUpdatingLocation];
      //  self.locationManager.delegate = nil;
      //  [self.locationManager release];
    }
    if ([state isEqualToString:@"Error"] && [self isLocationWaiter]){
        [self displayNoLocationWarning:kErrorMessageLocationUnableTitle andText:kErrorMessageLocationUnable];
    }
}

#pragma mark Selectors after response

- (void) displayNoLocationWarning:(NSString *)title andText:(NSString *) desc
{
    //((SharedMemory *)[SharedMemory sharedInstance]).localizationAvailable = NO;
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:desc delegate:self  cancelButtonTitle:@"Reintentar" otherButtonTitles:nil];
    [alert performSelectorOnMainThread:@selector(show) withObject:nil waitUntilDone:NO];
    [alert release];
}

- (void) performLocationProblemAction 
{
    UIAlertView *alert;
    alert = [[UIAlertView alloc] initWithTitle:kErrorMessageLocationOffTitle message:kErrorMessageLocationOff delegate:self  cancelButtonTitle:@"Activar" otherButtonTitles:nil];
    [alert performSelectorOnMainThread:@selector(show) withObject:nil waitUntilDone:NO];
    [alert release];
}


-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{    
    //Aceptar (From JSON error)
    if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:@"Ignorar"]){
        //((SharedMemory *)[SharedMemory sharedInstance]).localizationAvailable = NO;
        [self locationNotAvailablePostAction];
    }
    //Continuar (From Location error)
    else if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:@"Reintentar"]){
        //((SharedMemory *)[SharedMemory sharedInstance]).localizationAvailable = YES;
        [self startReadingLocation];
    }
    //Activar (When the service is off from the app)
    else if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:@"Activar"]){
        [self performLocationActivation];
    }
}

//Override to perform segue to the right screen
- (void) performLocationActivation
{
    [self startReadingLocation];
}

//Override to perform aditional actions after location couldnt be found
- (void) locationNotAvailablePostAction
{
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}



- (BOOL) isBetterLocation:(CLLocation*) newLocation  currentBestLocation:(CLLocation*) currentBestLocation
{
    if (currentBestLocation == nil || currentBestLocation.horizontalAccuracy<0) {
        // A new location is always better than no location
        return true;
    }
    
    if ([self isFakeLocation:currentBestLocation]) {
        return false;
    }
    
    // Check whether the new location fix is newer or older
    long timeDelta = [[newLocation timestamp] timeIntervalSince1970] - [[currentBestLocation timestamp] timeIntervalSince1970];
    BOOL isSignificantlyNewer = timeDelta > TWO_MINUTES;
    BOOL isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    BOOL isNewer = timeDelta > 0;
    
    // If it's been more than two minutes since the current location, use the new location
    // because the user has likely moved
    if (isSignificantlyNewer) {
        return true;
	    // If the new location is more than two minutes older, it must be worse
    } else if (isSignificantlyOlder) {
        return false;
    }
    
    // Check whether the new location fix is more or less accurate
    int accuracyDelta = (int) (newLocation.horizontalAccuracy - currentBestLocation.horizontalAccuracy);
    BOOL isLessAccurate = accuracyDelta > 0;
    BOOL isMoreAccurate = accuracyDelta <= 0;
    BOOL isSignificantlyLessAccurate = accuracyDelta > 200;
    
    // Check if the old and new location are from the same provider
    BOOL isFromSameProvider = true;
    
    // Determine location quality using a combination of timeliness and accuracy
    if (isMoreAccurate) {
        return true;
    } else if (isNewer && !isLessAccurate) {
        return true;
    } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
        return true;
    }
    return false;
}

@end
